/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package ca.trader.beans.enterprise;

import ca.trader.except.UUIDDoesNotExistException;
import ca.trader.except.UUIDExistsException;
import ca.trader.except.UserAlreadyExistsException;
import ca.trader.except.UserDoesNotExistException;
import ca.trader.except.UserNotAuthenticatedException;
import ca.trader.logger.TraderLogger;
import ca.trader.user.User;
import ca.trader.user.UserActivationDesc;
import ca.trader.user.UserEntityConsts;
import ca.trader.user.UserLoginHistoryRecord;
import ca.trader.utils.HttpConsts;
import ca.trader.utils.HttpUtils;
import java.util.UUID;
import javax.ejb.Stateless;
import javax.ejb.TransactionAttribute;
import javax.ejb.TransactionAttributeType;
import javax.persistence.EntityManager;
import javax.persistence.NoResultException;
import javax.persistence.PersistenceContext;
import javax.persistence.PersistenceContextType;
import javax.persistence.TypedQuery;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

/**
 * Stateless bean for performing actions on User entity objects
 *
 * @author Ahsan
 */
@Stateless
public class UserBean implements UserBeanLocal
{

	 private static final String _className = UserBean.class.getName();
	 /**
	  * Entity manager for persisting or retrieving user objects
	  */
	 @PersistenceContext(unitName = TraderPersistenceConsts.PERSISTENCE_UNIT_NAME, type = PersistenceContextType.TRANSACTION)
	 private EntityManager entityManager;

	 /**
	  * Get a user with a given email address.
	  *
	  * @param email the email address
	  * @return User with the given email address.
	  * @throws UserDoesNotExistException if a user with the given email
	  * address does not exist
	  * @exception IllegalArgumentException if the email address is null
	  */
	 @Override
	 @TransactionAttribute(TransactionAttributeType.REQUIRED)
	 public User getUserWithEmail(final String email) throws UserDoesNotExistException
	 {
		  String _methodName = "getUserWithEmail";

		  TraderLogger.logMethodEntry(_className, _methodName);

		  // Throw IllegalArgumentException if email is null
		  if (email == null)
		  {
			   TraderLogger.logWarning(_className, _methodName, "Email is null");
			   TraderLogger.logMethodExit(_className, _methodName);
			   throw new IllegalArgumentException("Email is null");
		  }

		  TraderLogger.logInfo(_className, _methodName, "Trying to find user with email " + email);


		  // Prepare query to find user by email
		  TypedQuery<User> queryUserByEmail = entityManager.createNamedQuery(UserEntityConsts.NAMED_QUERY_FIND_USER_BY_EMAIL, User.class);
		  queryUserByEmail.setParameter(UserEntityConsts.NAMED_QUERY_FIND_USER_BY_EMAIL_PARAM_EMAIL, email);

		  User u;

		  try
		  {
			   // find user by email
			   u = queryUserByEmail.getSingleResult();
		  }
		  catch (NoResultException ex)
		  {

			   TraderLogger.logWarning(_className, _methodName, "Unable to find user with email " + email);
			   TraderLogger.logMethodExit(_className, _methodName);

			   // Throw exception if no result (i.e. no user found)
			   throw new UserDoesNotExistException(email);

		  }


		  TraderLogger.logWarning(_className, _methodName, "Found user with email " + email + ", id " + u.getId());

		  TraderLogger.logMethodExit(_className, _methodName);

		  return u;
	 }

	 /**
	  * Find a user with a given email address.
	  *
	  * @param email the email address
	  * @return User with the given email address. If no user exists with the
	  * given email address, null is returned.
	  * @exception IllegalArgumentException if the email address is null
	  */
	 @Override
	 @TransactionAttribute(TransactionAttributeType.REQUIRED)
	 public User findUserWithEmail(final String email)
	 {
		  try
		  {
			   return getUserWithEmail(email);
		  }
		  catch (UserDoesNotExistException ex)
		  {
			   return null;
		  }
	 }

	 /**
	  * Get a user with the given ID
	  *
	  * @param id the user id
	  * @return User with the given id
	  * @throws UserDoesNotExistException if a user with the given id does not
	  * exist
	  * @exception IllegalArgumentException if the id is null
	  */
	 @Override
	 @TransactionAttribute(TransactionAttributeType.REQUIRED)
	 public User getUserWithID(final Integer id) throws UserDoesNotExistException
	 {
		  String _methodName = "getUserWithID";

		  TraderLogger.logMethodEntry(_className, _methodName);

		  // Throw IllegalArgumentException if id is null
		  if (id == null)
		  {
			   TraderLogger.logWarning(_className, _methodName, "id is null");

			   TraderLogger.logMethodExit(_className, _methodName);
			   throw new IllegalArgumentException("id is null");
		  }

		  TraderLogger.logInfo(_className, _methodName, "Trying to find user with id " + id);

		  // Find user with the ID
		  User u = entityManager.find(User.class, id);

		  // If the user is null (the find method returns null if no user with the id exists)
		  // then throw UserDoesNotExistException
		  if (u == null)
		  {
			   TraderLogger.logWarning(_className, _methodName, "Unable to find user with id " + id);
			   TraderLogger.logMethodExit(_className, _methodName);
			   throw new UserDoesNotExistException(id);
		  }

		  TraderLogger.logInfo(_className, _methodName, "Found user with id " + id + ", email " + u.getEmail());

		  TraderLogger.logMethodExit(_className, _methodName);

		  return u;
	 }

	 /**
	  * Get a user with a given id
	  *
	  * @param id the user id
	  * @return User with the given id. If no user exists with the given id,
	  * null is returned.
	  * @exception IllegalArgumentException if the id is null
	  */
	 @Override
	 @TransactionAttribute(TransactionAttributeType.REQUIRED)
	 public User findUserWithID(final Integer id)
	 {
		  try
		  {
			   return getUserWithID(id);
		  }
		  catch (UserDoesNotExistException ex)
		  {
			   return null;
		  }
	 }

	 /**
	  * *
	  * inserts a user entity bean into the database. Throws
	  * IllegalArgumentException if the bean is null
	  *
	  * @param user the user to insert
	  * @throws UserAlreadyExistsException if a user with the same email
	  * address as the user already exists
	  * @exception IllegalArgumentException if the user entity object is null
	  */
	 @Override
	 @TransactionAttribute(TransactionAttributeType.REQUIRED)
	 public void saveUser(final User user) throws UserAlreadyExistsException
	 {

		  String _methodName = "saveUser";

		  TraderLogger.logMethodEntry(_className, _methodName);

		  // If the user object is null, throw IllegalArgumentException
		  if (user == null)
		  {
			   TraderLogger.logWarning(_className, _methodName, "user is null");

			   TraderLogger.logMethodExit(_className, _methodName);
			   throw new IllegalArgumentException("user is null");
		  }

		  TraderLogger.logInfo(_className, _methodName, "Trying to register user with email " + user.getEmail());

		  // Try to find a user with the email that this user has.
		  if (findUserWithEmail(user.getEmail()) != null)
		  {

			   TraderLogger.logWarning(_className, _methodName, "User already exists with email  " + user.getEmail());
			   TraderLogger.logMethodExit(_className, _methodName);

			   // Throw UserAlreadyExistsException if we found a user with that email
			   throw new UserAlreadyExistsException(user.getEmail());
		  }

		  // Save the user in the DB
		  entityManager.persist(user);

		  TraderLogger.logMethodExit(_className, _methodName);
	 }

	 /**
	  * Inserts a User and UserActivationDesc entity bean into the database The
	  * User entity bean is an attribute in the UserActivationDesc bean,
	  * extracted via the getUser method in the UserActivationDesc bean.
	  *
	  * @param desc the user activation object.
	  * @throws UserAlreadyExistsException if a user with the email (attribute
	  * of the User entity bean) already exists
	  * @exception IllegalArgumentException if the ActivationDesc is null
	  */
	 @Override
	 @TransactionAttribute(TransactionAttributeType.REQUIRED)
	 public void saveUserWithActivationDesc(final UserActivationDesc desc) throws UserAlreadyExistsException
	 {
		  String _methodName = "saveUserWithActivationDesc";

		  TraderLogger.logMethodEntry(_className, _methodName);

		  // if the desc object is null, throw IllegalArgumentException
		  if (desc == null)
		  {
			   TraderLogger.logWarning(_className, _methodName, "ActivationDesc is null");

			   TraderLogger.logMethodExit(_className, _methodName);
			   throw new IllegalArgumentException("ActivationDesc is null");

		  }

		  // save the user first, extracted via the getUser method
		  saveUser(desc.getUser());

		  // now save the desc object
		  entityManager.persist(desc);

		  TraderLogger.logMethodExit(_className, _methodName);
	 }

	 /**
	  * Finds a user login history record with the given UUID
	  *
	  * @param UUID the UUID to look up
	  * @return the UserLoginHistoryRecord with the given UUID. Returns null if
	  * no UserLoginHistoryRecord exists with the given UUID.
	  */
	 @Override
	 @TransactionAttribute(TransactionAttributeType.REQUIRED)
	 public UserLoginHistoryRecord findUserLoginHistoryRecord(String UUID)
	 {

		  String _methodName = "getUserLoginHistoryRecord";
		  TraderLogger.logMethodEntry(_className, _methodName);

		  TraderLogger.logInfo(_className, _methodName, "Checking if user remember record exists in DB with UUID " + UUID);

		  // if the UUID is null, return null;
		  if (UUID == null)
		  {
			   TraderLogger.logWarning(_className, _methodName, "UUID is null. Exiting.");
			   TraderLogger.logMethodExit(_className, _methodName);
			   return null;
		  }

		  // Find the UserLoginHistoryRecord object
		  UserLoginHistoryRecord loginRecord = entityManager.find(UserLoginHistoryRecord.class, UUID);


		  if (loginRecord == null)
		  {
			   TraderLogger.logWarning(_className, _methodName, "Unable to find login record with matching UUID.");
		  }
		  else
		  {
			   TraderLogger.logInfo(_className, _methodName, "Found login record with matching UUID.");
		  }

		  TraderLogger.logMethodExit(_className, _methodName);

		  // return the object
		  return loginRecord;
	 }

	 /**
	  * Creates and inserts a UserLoginHistoryRecord for the given user, and
	  * saves a cookie in the user's browser so that the next time they login,
	  * they are automatically authenticated. This feature is more informally
	  * known as "Remember Me" feature.
	  *
	  * @param response the HttpServletResponse object. User for setting the
	  * cookie
	  * @param user the user to "remember"
	  */
	 @Override
	 @TransactionAttribute(TransactionAttributeType.REQUIRED)
	 public void rememberUser(HttpServletResponse response, User user)
	 {
		  String _methodName = "rememberUser";

		  TraderLogger.logMethodEntry(_className, _methodName);

		  // create a uuid
		  String uuid = UUID.randomUUID().toString();

		  // flag for tracking whether the UserLoginHistoryRecord entity has been inserted
		  boolean inserted = false;

		  // Create the UserLoginHistoryRecord entity object
		  UserLoginHistoryRecord loginRecord = new UserLoginHistoryRecord();

		  // Add the user in the login record entity
		  loginRecord.setUser(user);

		  // keep attempting to insert while the 
		  while (!inserted)
		  {
			   //set the uuid in the login record entity
			   loginRecord.setUUID(uuid);

			   try
			   {
				    // insert the login record entity
				    insertUserLoginHistoryRecord(loginRecord);
				    inserted = true;
			   }
			   catch (UUIDExistsException ex) // if a login record with the same UUID already exists, refresh the uuid and try again
			   {
				    uuid = UUID.randomUUID().toString();
			   }
		  }

		  // create a cookie with the uuid
		  HttpUtils.addCookie(response, HttpConsts.COOKIE_USER_LOGIN_REMEMBER_UUID, uuid, HttpConsts.MAX_USER_REMEMBER_COOKIE_AGE);

		  TraderLogger.logMethodExit(_className, _methodName);
	 }

	 /**
	  * "Forgets" a user which has already been "remembered". Removes the
	  * UserLoginHistoryRecord entity with the same UUID as the cookie that the
	  * user has in their browser. It then removes the cookie from the users
	  * browser.
	  *
	  * @param request the HttpServletRequest object
	  * @param response the HttpServletResponse object
	  */
	 @Override
	 @TransactionAttribute(TransactionAttributeType.REQUIRED)
	 public void forgetUser(HttpServletRequest request, HttpServletResponse response)
	 {
		  String _methodName = "forgetUser";

		  TraderLogger.logMethodEntry(_className, _methodName);

		  TraderLogger.logInfo(_className, _methodName, "Attempting to delete user cookie from DB");

		  // Get the uuid from the cookie
		  String uuid = HttpUtils.getCookieValue(request, HttpConsts.COOKIE_USER_LOGIN_REMEMBER_UUID);

		  // if the cookie is not null
		  if (uuid != null)
		  {
			   // Remove the cookie from the browser and delete the UserLoginHistoryRecord entity from the DB
			   try
			   {
				    HttpUtils.removeCookie(response, HttpConsts.COOKIE_USER_LOGIN_REMEMBER_UUID);
				    deleteUserLoginHistoryRecord(uuid);

				    TraderLogger.logInfo(_className, _methodName, "UUID deleted successfully, user forgotten");
			   }
			   catch (UUIDDoesNotExistException ex)
			   {
				    TraderLogger.logWarning(_className, _methodName, "UUID deleted as cookie, but not found in database");
			   }
			   catch (Exception ex)
			   {
				    TraderLogger.logSevere(_className, _methodName, "Unknown exception", ex);

			   }

		  }
		  else
		  {
			   TraderLogger.logInfo(_className, _methodName, "User remember cookie not set.");
		  }


		  TraderLogger.logMethodExit(_className, _methodName);
	 }

	 /**
	  * Insert a UserLoginHistoryRecord entity into the DB Throw
	  *
	  * @param loginRecord the UserLoginHistoryRecord to insert
	  * @throws UUIDExistsException if a UserLoginHistoryRecord entity already
	  * exists in the DB with the same UUID as the entity being passed
	  */
	 @Override
	 @TransactionAttribute(TransactionAttributeType.REQUIRED)
	 public void insertUserLoginHistoryRecord(UserLoginHistoryRecord loginRecord) throws UUIDExistsException
	 {

		  String _methodName = "insertUserLoginHistoryRecord";

		  TraderLogger.logMethodEntry(_className, _methodName);

		  // if the UserLoginHistoryRecord is null, throw IllegalArgumentException is null
		  if (loginRecord == null)
		  {
			   TraderLogger.logWarning(_className, _methodName, "loginRecord is null. Exiting");
			   TraderLogger.logMethodExit(_className, _methodName);
			   throw new IllegalArgumentException("loginRecord is null");
		  }

		  TraderLogger.logInfo(_className, _methodName, "Checking if UUID already exists in DB: " + loginRecord.getUUID());

		  // If a UserLoginHistoryRecord entity already exists in the DB with the same UUID, throw an IllegalArgumentException 
		  if (findUserLoginHistoryRecord(loginRecord.getUUID()) != null)
		  {
			   TraderLogger.logWarning(_className, _methodName, "UUID already exists in DB, aborting");
			   TraderLogger.logMethodExit(_className, _methodName);

			   throw new UUIDExistsException(loginRecord.getUUID());
		  }
		  else
		  {
			   TraderLogger.logInfo(_className, _methodName, "UUID does not exist in DB.  Attempt to insert: " + loginRecord.getUUID());
		  }

		  // save the UserLoginHistoryRecord entity in the database
		  entityManager.persist(loginRecord);

		  TraderLogger.logInfo(_className, _methodName, "Successfully inserted login record");

		  TraderLogger.logMethodExit(_className, _methodName);
	 }

	 /**
	  * deletes a UserLoginHistoryRecord with the given UUID Throws
	  * UUIDDoesNotExistException if no UserLoginHistoryRecord with the given
	  * UUID
	  *
	  * @param UUID the uuid of the UserLoginHistoryRecord to delete
	  * @throws UUIDDoesNotExistException if no UserLoginHistoryRecord exists
	  * with the given UUID
	  * @exception IllegalArgumentException if the passed UUID is null
	  */
	 @Override
	 @TransactionAttribute(TransactionAttributeType.REQUIRED)
	 public void deleteUserLoginHistoryRecord(String UUID) throws UUIDDoesNotExistException
	 {
		  String _methodName = "deleteUserLoginHistoryRecordUUID";

		  TraderLogger.logMethodEntry(_className, _methodName);

		  TraderLogger.logInfo(_className, _methodName, "Attempting to delete UUID from DB: " + UUID);

		  // Find a UserLoginHistoryRecord.  The function will throw IllegalArgumentException if the uuid is null
		  UserLoginHistoryRecord loginRecord = findUserLoginHistoryRecord(UUID);

		  // if the UserLoginHistoryRecord returned is null, throw UUIDDoesNotExistException
		  if (loginRecord == null)
		  {
			   TraderLogger.logWarning(_className, _methodName, "UUID does not exist in DB, aborting");
			   TraderLogger.logMethodExit(_className, _methodName);

			   throw new UUIDDoesNotExistException(UUID);
		  }

		  // remove the UserLoginHistoryRecord from the db
		  entityManager.remove(loginRecord);

		  TraderLogger.logInfo(_className, _methodName, "Successfully deleted UUID from table: " + UUID);

		  TraderLogger.logMethodExit(_className, _methodName);
	 }

	 /**
	  * Gets the authenticated user associated with the given HttpSession. This
	  * done by extracting the "AuthenticatedUserID" attribute from the
	  * session, which contains the ID of the authenticated user. Then we find
	  * the user with that ID. The user is not set as authenticated. This
	  * function merely finds the user with the User ID in the session
	  * attribute "AuthenticatedUserID"
	  *
	  * @param session The HttpSession to search the user in
	  * @return the authenticated user.
	  * @throws UserDoesNotExistException if no user exists with the User ID in
	  * the session attribute "AuthenticatedUserID"
	  * @throws UserNotAuthenticatedException if there is no currently
	  * authenticated user in the session
	  */
	 @Override
	 @TransactionAttribute(TransactionAttributeType.REQUIRED)
	 public User getAuthenticatedUser(HttpSession session) throws UserDoesNotExistException, UserNotAuthenticatedException
	 {
		  return getUserWithID(HttpUtils.getAuthenticatedUserID(session));
	 }

}